package sketch.generator;

import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

import randoop.RMethod;
import randoop.StatementKind;
import sketch.ExecutableSequenceContent;
import sketch.MethodExecOutcome;
import sketch.SMethod;
import sketch.SketchSequence;
import sketch.generator.examples.SequentialValue;
import sketch.util.Checker;
import sketch.util.Globals;
import sketch.util.RandoopUtil;
import sketch.util.SketchUtil;

public class AssertionGenerator<T extends SketchSequence> extends SequenceGenerator<T> {

	//private final SequenceGenerator<T> sequenceGenerator;
	
    public SketchSequence generatedSequence = null;
	
	public MethodExecOutcome expectedOutcome = null;
	
	public AssertionGenerator(Object receiver, SMethod smethod, ValueGenerator<? extends Object> generator) {
		super(receiver, smethod, generator);
		//sequenceGenerator = new InorderSequenceGenerator<T>(receiver, smethod, generator); 
	}
	
	public void setExpectedObject(Object obj) {
		this.expectedOutcome = new MethodExecOutcome();
		this.expectedOutcome.setOutcomeValue(obj);
	}
	
	public void setExpectedOutcome(MethodExecOutcome outcome) {
		Checker.checkNull(outcome, "The method outcome should not be null!");
		this.expectedOutcome = outcome;
	}

	@Override
	protected SketchSequence generateNext() {
		//System.out.println("-- call generateNext() in AssertionGenerator");
		if(!isReset()) {
			throw new IterationException("Need to call reset before calling generating next!");
		}
		this.generatedSequence = composeSequence();
		return this.generatedSequence;
	}

	@Override
	public void reset() {
		super.reset();
		this.generatedSequence = null;
		//this.sequenceGenerator.reset();
	}
	
	@Override
	public boolean hasNext() {
		return isReset();
	}
	
	@Override
	public ExecutableSequenceContent[] executables() {
		List<String> methods = this.smethod.methodNames;
		ExecutableSequenceContent[] contents = new ExecutableSequenceContent[methods.size()];
		int index = 0;
		for(String method : methods) {
			StatementKind s = GenTests.lookupForStatement(this.smethod.sclazz.clazz, method);
			Checker.checkNull(s, "StatementKind for method: " + method + " could not be null!");
			//that is a big difference, use current()
			//Object arg = this.valueGenerator.current();
			
			ExhaustiveTypedValueGenerator<Object> gen = this.typedValueGenerators.get(method);
			SequentialValue<Object> objList = gen.current();
			Object[] args = objList.getValues().toArray(new Object[0]);
			
			boolean hasArgument = (RandoopUtil.getParamNum(s) != 0);
			ExecutableSequenceContent content = hasArgument ?
					new ExecutableSequenceContent(this.getReceiver(), s, args) :
						new ExecutableSequenceContent(this.getReceiver(), s);
			contents[index] = content;
			
			index++;
		}
		
		return contents;
	}
	
//	@Override
//	public Object[] evaluate() {
//		//for debugging purpose
//		StringBuilder sb = new StringBuilder();
//		
//		List<String> methods = this.smethod.methodNames;
//		Object[] results = new Object[methods.size()];
//		int index = 0;
//		for(String method : methods) {
//			StatementKind s = GenTests.lookupForStatement(this.smethod.sclazz.clazz, method);
//			Checker.checkNull(s, "StatementKind for method: " + method + " could not be null!");
//			//that is a big difference, use current()
//			Object arg = this.valueGenerator.current();
//			boolean hasArgument = (RandoopUtil.getParamNum(s) != 0);
//			ExecutableSequenceContent content = hasArgument ?
//					new ExecutableSequenceContent(this.getReceiver(), s, arg) :
//						new ExecutableSequenceContent(this.getReceiver(), s);
//			results[index] = content.excute().getReturnObject();
//			
//			sb.append("in assertion gen, executing: " + content.toString() + " with result: " + results[index]);
//			sb.append(Globals.lineSep);
//			
//			index++;
//		}
//		
//		//for debugging
//		System.out.println(sb.toString());
//		
//		//it would return null
//		return results;
//	}

	/**
	 * Only for composing sequence text
	 * */
	@Override
	protected SketchSequence composeSequence() {

		List<String> methods = this.smethod.methodNames;
		
		SketchSequence sequence = new SketchSequence();
		for(String method : methods) {
			String smethodName = method;
			Method m = this.smethod.findMethod(smethodName);
			Checker.checkNull(m, "There is no method for: " + method);
			StatementKind s = GenTests.lookupForStatement(this.smethod.sclazz.clazz, method);
			Checker.checkNull(s, "StatementKind for method: " + method + " is null!");
			
			method = SketchUtil.removeMethodParam(method);
//			Checker.checkTrue(this.valueGenerator.hasNext(), "No more values available!");
//			Object arg = this.valueGenerator.next();
//			
//			boolean hasArgument = RandoopUtil.hasParam(s);
//			if(!hasArgument) {
//				arg = "";
//			}
			
			//newly added
			List<Object> objList = new LinkedList<Object>();
			if(RandoopUtil.hasParam(s)) {
			  ExhaustiveTypedValueGenerator<Object> gen = this.typedValueGenerators.get(smethodName);
			  Checker.checkTrue(gen.hasNext(), "No more values");
			  SequentialValue<Object> seqValues = gen.next();
			  objList = seqValues.getValues();
			}
			
			Checker.checkTrue(m.getParameterTypes().length == objList.size(), "The size are not equal!");
			
			//sequence.addText(SketchSequence.RECEIVER + "." + method + "(" +  arg + ");");
			//sequence.addText("  " + SketchSequence.RECEIVER + "  argument list: " + objList);
			String code = SketchSequence.RECEIVER + "." + method + "(";
			for(int i = 0; i < objList.size(); i++) {
				if(i != 0) {
					code = code + ",";
				}
				code = code + objList.get(i);
			}
			code = code + ");";
			sequence.addText(code);
		}
		return sequence;
	}

	@Override
	public SketchSequence getGeneratedSequence() {
		Checker.checkNull(this.generatedSequence, "There is no sequence generated in AssertionGenerator.");
		return this.generatedSequence;
	}

//	@Override
//	protected MethodExecOutcome[] executionResult() {
//		//under the assumption that there is only 1 method
//		//if that method has a void type, we will throw an error
//		MethodExecOutcome[] outcomes = new MethodExecOutcome[this.smethod.methodNames.size()];
//        List<String> methods = this.smethod.methodNames;
//		for(int i = 0; i < methods.size(); i++) {
//			String method = methods.get(i);
//			StatementKind s = GenTests.lookupForStatement(this.smethod.sclazz.clazz, method);
//			//only for non-constructor
//			Class<?> retType = RandoopUtil.getReturnType(s);
////			Checker.checkTrue(!retType.equals(void.class), "There is no output type for void type!");
////			
//			if(retType.equals(void.class)) {
//				return null;
//			}
//			
//			//got a method execution outcome
//			outcomes[i] = new MethodExecOutcome();
//		}
//		
//		return outcomes;
//	}

}
